package bme.data {
	import com.adobe.serialization.json.JSON;

	import org.osflash.signals.Signal;

	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.HTTPStatusEvent;
	import flash.events.IOErrorEvent;
	import flash.events.ProgressEvent;
	import flash.events.SecurityErrorEvent;
	import flash.events.TimerEvent;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.net.URLRequestMethod;
	import flash.net.URLStream;
	import flash.utils.Timer;

	/**
	 * Responsible for loading and parsing data from Burning Man Earth.
	 * @author Rendall
	 */
	public class BMELoader {
		private static const DEFAULT_TIMEOUT_SECS : uint = 15;

		public static const STATUS_READY : String = "STATUS_READY";
		public static const STATUS_REQUEST_SENT : String = "STATUS_REQUEST_SENT";
		public static const STATUS_RESPONSE_RECEIVED : String = "STATUS_RESPONSE_RECEIVED";

		public static const STATUS_ERROR : String = "STATUS_ERROR";
		public static const STATUS_TIMEOUT : String = "STATUS_TIMEOUT";

		public static const STATUS_LOAD_PROGRESS : String = "STATUS_LOAD_PROGRESS";
		public static const STATUS_LOAD_OPEN : String = "STATUS_LOAD_OPEN";
		public static const STATUS_LOAD_CANCEL : String = "STATUS_LOAD_CANCEL";
		public static const STATUS_PARSE_PROGRESS : String = "STATUS_PARSE_PROGRESS";
		public static const STATUS_PARSE_COMPLETE : String = "STATUS_PARSE_COMPLETE";

		public static const STATUS_NEW_ITEM : String = "STATUS_NEW_ITEM";
		
			
		private var _timeOutSeconds : uint = DEFAULT_TIMEOUT_SECS;

		private var urlReq : URLRequest;
		private var _statusSignal : Signal;
		private var timeoutTimer : Timer;
		private var bmeURLLoader : URLStream;
		private var dataArray : Array;
		private var itemIndex : uint;
		private var parseTimer : Timer;

		//private var bmeURLLoader : URLLoader;

		
		
		public function BMELoader( $ : SingletonEnforcer = null) {
			if (!$) {
				throw new ArgumentError("BMELoader is a Singleton and may only be accessed via its accessor methods: 'getInstance()' or '$'.");      
			}

			startReadyTimer();
			//testInit();
		}

		private function startReadyTimer() : void {
			var readyTimer : Timer = new Timer(10, 1);
			readyTimer.addEventListener(TimerEvent.TIMER_COMPLETE, onReadyTime, false, 0, true);
			readyTimer.start();
		}

		private function onReadyTime(event : TimerEvent) : void {
			var timer : Timer = event.target as Timer;
			timer.removeEventListener(TimerEvent.TIMER_COMPLETE, onReadyTime);
			setStatus(STATUS_READY);
		}

		public function set timeOutSeconds(value : uint) : void {
			_timeOutSeconds = value;	
		}

		public function sendRequest(url : String) : void {
			urlReq = new URLRequest();
			urlReq.url = url;
			//urlReq.contentType = CONTENT_TYPE;

			urlReq.method = URLRequestMethod.POST;
			
			//bmeURLLoader = new URLLoader();
			bmeURLLoader = new URLStream();
			
			configureListeners(bmeURLLoader);
			//closeStream();
			//bmeStream = new URLStream();
			
			//configureListeners(bmeStream);

			try {
				//bmeStream.load(urlReq);
				bmeURLLoader.load(urlReq);
				setStatus(STATUS_REQUEST_SENT);
			} catch (error : Error) {
				trace("\tNot sent error : " + error);
				
				setStatus(STATUS_ERROR, error.message);
			}
		}

		private function configureListeners(dispatcher : EventDispatcher) : void {
			dispatcher.addEventListener(Event.COMPLETE, onResponseReceived);
			dispatcher.addEventListener(HTTPStatusEvent.HTTP_STATUS, onHTTPResponse);
			dispatcher.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
			dispatcher.addEventListener(Event.OPEN, onBMEOpen);
			// if (dispatcher is URLStream) dispatcher.addEventListener(ProgressEvent.PROGRESS, onBMEStreamProgress);
			// else 
			dispatcher.addEventListener(ProgressEvent.PROGRESS, onBMEProgress);
			dispatcher.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError);
		}

		
		private function onBMEStreamProgress(event : ProgressEvent) : void {
			trace("\n BMELoader.onBMEStreamProgress( event:" + event + ")");
			var progress : String = Math.floor(100 * event.bytesLoaded / event.bytesTotal) + "%";
			setStatus(STATUS_LOAD_PROGRESS, progress);
		}

		
		private function onBMEProgress(event : ProgressEvent) : void {
			trace("\n BMELoader.onBMEProgress( event:" + event + ")");
			trace("\tevent.bytesLoaded, events.bytesTotal : " + event.bytesLoaded, event.bytesTotal);
			
			var progress : Number = event.bytesLoaded / event.bytesTotal;
			
			if (progress == Infinity) progress = event.bytesLoaded;

			setStatus(STATUS_LOAD_PROGRESS, progress);
		}

		private function onBMEOpen(event : Event) : void {
			trace("\n BMELoader.onBMEOpen( event:" + event + ")");
			setStatus(STATUS_LOAD_OPEN);
		}

		private function onSecurityError(event : SecurityErrorEvent) : void {
			trace("\n BMELoader.onSecurityError( event:" + event + ")");
			setStatus(STATUS_ERROR, event.text);
		}

		private function onIOError(event : IOErrorEvent) : void {
			trace("\n BMELoader.onIOError(" + event + ")");
			trace("\terrorID : " + event.type);
			
			setStatus(STATUS_ERROR, event.text);
		}

		private function onHTTPResponse(event : HTTPStatusEvent) : void {
			//Useless
		}

		private function onResponseReceived(event : Event) : void {
			
			setStatus(STATUS_RESPONSE_RECEIVED);
			
			var data : String;

			if (event.target is URLLoader) data = URLLoader(event.target).data;
			if (event.target is URLStream) data = URLStream(event.target).readMultiByte(URLStream(event.target).bytesAvailable, "utf-8")
			
			//trace ("\tdata : " + data);

			try {
				dataArray = JSON.decode(data);
			}catch(error : Error) {
				trace(error);
				setStatus(STATUS_ERROR, error.message);
				trace("\tdata : " + data);
			}
			
			
			if (dataArray is Array) {
				dataArray.sortOn("id", Array.NUMERIC);
				
				// A for-next loop causes the app to lock and hang
				// we're starting a timer pulse to take care
				// of each item.

				parseTimer = new Timer(10, dataArray.length);
				parseTimer.addEventListener(TimerEvent.TIMER, parseItem);
				itemIndex = 0;
				parseTimer.start();

				//for (var i : int = 0;i < dataArray.length;i++) parseItem();


			}		
			
			//setStatus(STATUS_PARSE_COMPLETE);				//startReadyTimer();	
		}

		private function parseItem(event : TimerEvent = null) : void {
			var parseObj : Object = dataArray[itemIndex];
			var BRCClass : Class = BRC_DataType.getMainClass(parseObj);
			var bmeThing : * = new BRCClass(parseObj);
			setStatus(STATUS_NEW_ITEM, bmeThing);
			itemIndex++;
			if (itemIndex == dataArray.length) {
				stopParsing();
			}	
			else setStatus(STATUS_PARSE_PROGRESS, itemIndex + "/" + dataArray.length);
		}

		private function stopParsing() : void {
			if (parseTimer) {
				if (parseTimer.running) parseTimer.stop();
				parseTimer.removeEventListener(TimerEvent.TIMER, parseItem);	
				parseTimer = null;
			}
			setStatus(STATUS_PARSE_COMPLETE);	
			// trace("\tdataObj.length : " + dataArray.length);
			startReadyTimer();	
		}

		
		private static function isEmptyString(str : String) : Boolean {
			return (str == null || str == "" || str.length == 0);	
		}

		private function setStatus(statusType : String, value : * = null) : void {
			trace("\n BMELoader.setStatus( status:" + statusType, value + ")");	
			statusSignal.dispatch(statusType, value);
			
			// start/stop timeout timer
			var startStatuses : Array = [STATUS_REQUEST_SENT,STATUS_LOAD_PROGRESS,STATUS_LOAD_OPEN];
			var isStartStatus : Boolean = startStatuses.indexOf(statusType) >= 0;
			var stopStatuses : Array = [STATUS_ERROR,STATUS_RESPONSE_RECEIVED,STATUS_TIMEOUT];
			var isStopStatus : Boolean = stopStatuses.indexOf(statusType) >= 0;
			
			if (isStartStatus) startTimeout();
			else if (isStopStatus) stopTimeout();
		}

		
		private function startTimeout() : void {
			if (!timeoutTimer) {
				timeoutTimer = new Timer(_timeOutSeconds * 1000, 1);
				timeoutTimer.addEventListener(TimerEvent.TIMER_COMPLETE, onTimeout, false, 0, true);
			}
			
			timeoutTimer.reset();
			timeoutTimer.start();
		}

		private function onTimeout(event : TimerEvent) : void {
			closeStream();
			setStatus(STATUS_TIMEOUT);
		}

		private function stopTimeout() : void {
			if (!timeoutTimer) return;
			timeoutTimer.stop();
			timeoutTimer.removeEventListener(TimerEvent.TIMER_COMPLETE, onTimeout);
			timeoutTimer = null;
		}

		/*
		 * statusSignal(statusType, message);
		 */
		public function get statusSignal() : Signal {
			if (!_statusSignal) _statusSignal = new Signal(String, Object);
			return _statusSignal;
		}

		public function cancelRequest() : void {
			stopTimeout();
			if (bmeURLLoader) {
				closeStream();
				setStatus(STATUS_LOAD_CANCEL, "User cancelled loading.");
			}
			if (parseTimer) stopParsing();
		}

		private function closeStream() : void {
			if (!(bmeURLLoader is URLStream)) return;
			if (bmeURLLoader && bmeURLLoader.connected) bmeURLLoader.close();
			bmeURLLoader = null;
		}

		protected static var _instance : BMELoader;

		
		/**
		 * Singleton Accessor
		 * @return BMELoader
		 */
		public static function getInstance() : BMELoader {
			if( _instance == null ) _instance = new BMELoader(new SingletonEnforcer());
			return _instance;
		}

		/**
		 * Shorthand singleton accessor getter
		 * @return BMELoader
		 */
		public static function get instance() : BMELoader {
			return BMELoader.getInstance();        
		}
	}
}

internal class SingletonEnforcer {
}      